/*
 * VeriSimDocumentFrame.java
 * 
 * last update: 14.01.2010 by Stefan Saru
 * 
 * author:	Felix(felix@ulise.cs.pub.ro)
 *
 * Obs: N/A
 */

package ide;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.net.URL;
import java.util.*;

import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.event.*;
import javax.swing.*;
import javax.swing.plaf.*;

/**
 * Sample JInternalFrame using the simple text editor component that
 * supports only one font. (for now)
 *
 * @author  Lucian Felix Ghita
 * @bugfixes Alec  <-+
 * @alecfixes fEliX  |
 * @-----------------+
 * @version 1.0
 */
@SuppressWarnings("serial")
class VeriSimDocumentFrame
extends JInternalFrame implements UndoableEditListener
{

	private static int editorCount = 1;
	private static ResourceBundle resources;
	private String fileName;
	public Project theProject;  //null means that this file is not included in the current project
	//the comment from above means nothing.

	private JScrollPane scroller;
	private int fontHeight;
	public boolean focused;
	public int windowID;

	public VerilogTextArea editor;
	private Hashtable commands;
	private Hashtable menuItems;
	private JMenuBar menubar;
	private JToolBar toolbar;
	private JComponent status;

	public NumberedBorder numberedBorder;


	/** UndoManager that we add edits to.  */
	protected UndoManager undo = new UndoManager();

	private UndoAction undoAction = new UndoAction();
	private RedoAction redoAction = new RedoAction();

	/**
	 * File information
	 */
	public static String fileDataProp = "fileData";

	static {
		//set the appropriate UI for editor:
		//veriLogTextUI is the UI class ID for UIDefaults
		UIManager.put(VerilogTextArea.uiClassID, "ide.VerilogTextUI"); 
	}

	VeriSimDocumentFrame(Project project, File file) throws IOException
	{
		super("New editor frame", true, true, true, true);
		this.theProject = project;

		//focused = true;
		//open existing file or creates a new file if file == null;
		initEditor();

		if (file == null) {//new file
			setTitle("Untitled" + editorCount + ".v");
			fileName = "Untitled" + editorCount + ".v";
			editorCount++;
		} else {
			setTitle("" + file);
			fileName = "" + file;
			if (file.exists())
				loadFile();
		}

		editor.getDocument().addUndoableEditListener(this); // Add this as a listener for undoable edits.
		FileData fileData = new FileData(fileName);
		editor.getDocument().putProperty(fileDataProp, fileData);
	}

	/**
	 * Makes a new view based on the document from 'original'.
	 * NOTE: since from this moment the document will have both views as
	 * change listeners, the undo command will ... I don't know..
	 */
	VeriSimDocumentFrame(Project project, Document doc) {
		super("New editor frame", true, true, true, true);
		this.theProject = project;

		//focused = true;
		//open existing file or creates a new file if file == null;
		initEditor();
		FileData fd = (FileData) doc.getProperty(fileDataProp);
		fd.openCount++;
		fileName = fd.fileName;

		setTitle(fileName + " (" + fd.openCount + ")");
		editor.setDocument(doc);
		doc.addUndoableEditListener(this); // Add this as a listener for undoable edits.
	}
	/* sa-l ia dracu' de JIT  compiler.Crapa mashina virtuala cu totul! (GPF in Msvcrt.dll sau in Kernel32.dll)
     JAVA caused an invalid page fault in module KERNEL32.DLL at 014f:bff85fe2.
     Registers:
     EAX=c00211fc CS=014f EIP=bff85fe2 EFLGS=00010202
     EBX=00000000 SS=0157 ESP=01ca0000 EBP=01ca0098
     ECX=c00211f8 DS=0157 ESI=01ca00cc FS=3867
     EDX=c00211fc ES=0157 EDI=01ca00b0 GS=0000
     Bytes at CS:EIP: 56 8b 19 57 8b 30 55 83 7e 54 00 0f 84 1c 01 00
     Stack dump: 00000000 00000000 bff85812 00000001 0000000e 01ca00b0 01ca00cc 81572840 8156a1bc 00000000 00000000 00000000 00000000 00000000 00000000 00000000
	 */

	private void initEditor() {
		//editor.setFont(new Font("FixedSys", Font.PLAIN, 12));  //e cumva prea mult 12 ?
		//todo -> the font should be chosed from the preferences dialog from the getFontList() list;
		scroller = new JScrollPane(editor = new VerilogTextArea());
		JViewport vport = scroller. getColumnHeader();
		getContentPane().add("Center", scroller);
		setSize( 325, 200);
		setLocation( 20 + editorCount * 20, 55 + editorCount * 20);
		fontHeight = editor.getFont().getSize();  //shi cu spatiile dintre linii ce fac?
		addInternalFrameListener(new EditorInternalFrameAdapter(this, theProject));
		editor.getCaret().
		addChangeListener(new CaretPositionChangeListener(theProject.sb,
				editor)); 
		editor.getCaret().setBlinkRate(0);
		//fara blink scap de repainturi la tot editorul..!
		editor.setBorder(numberedBorder =
			new NumberedBorder(35, scroller.getViewport(), editor.getRowHeight()));
		setCursor(new Cursor(Cursor.TEXT_CURSOR));

		//din motive necunoscute (*#!?) JTextArea.getRowHeight() nu e publica..;
		//deci nu pot afla inaltimea unei linii,
		//ceea ce e un lucru foarte frustrant la ora 5 am...
	}



	public void loadFile() throws IOException{
		editor.read(new FileReader(fileName), null);
	} 

	public void saveFile() throws IOException{
		//todo : System.setProperty("line.separator", optiunea din ...);
		editor.write(new FileWriter(fileName));
		getFileData().isModified = false;
	}

	public String getFileName() {
		return fileName;
	}

	public FileData getFileData() {
		return (FileData) editor.getDocument().getProperty(fileDataProp);
	}

	/**
	 * Renames the file associated with this editor. This implies
	 * creating a new PlainDocument for this editor.
	 * @return the old document
	 */
	public Document setFileName(String fileName) throws IOException {
		Document newDoc =  null, oldDoc = editor.getDocument();
		String oldFileName = fileName;
		try {
			setTitle(this.fileName = fileName);
			saveFile(); //create the new file
			loadFile(); //create a new Document for it
			newDoc = editor.getDocument();
			FileData fd = new FileData(fileName);
			newDoc.putProperty(fileDataProp, fd);
			newDoc.addUndoableEditListener(this);
			return oldDoc;
		} catch (IOException ioex) {
			fileName = oldFileName;
			if (editor.getDocument() != oldDoc)editor.setDocument(oldDoc);
			throw ioex; //notify that we failed here
		} 
	}


	/**
	 * Messaged when the Document has created an edit, the edit is
	 * added to <undo>, an instance of UndoManager.
	 */
	public void undoableEditHappened(UndoableEditEvent e) {
		undo.addEdit(e.getEdit());
		undoAction.update();
		redoAction.update();
		FileData fileData = (FileData) editor.getDocument().getProperty(fileDataProp);
		if (!fileData.isModified) { //WHY the f.. do you modify the main title from
			//here????
			//if the same document will be opened in multiple views, only some of them
			//will have the modified marker active....
			fileData.isModified = true;
		}
	}

	public void jumpToLine(int line) {
		highlightLine(line-1);
		System.out.println("rows: " + editor.getRows());
		scroller.getViewport().setViewPosition(new Point(0,
				editor.getRowHeight() *
				(line - 1)));
		//todo ! - > fontHeigth != lineHeigth 
	}

	public int getCurrentLine() {
		try {
			return editor.getLineOfOffset(editor.getCaretPosition());
		} catch (Exception e) {
			theProject.error("Internal error in \n VeriSimDocumentFrame.getCurrentLine :\n" + e);
			return 0; // sau mai bine null
		}
	}

	public void highlightLine(int line) {
		//todo; method for highlight in NumberedBorder
		numberedBorder.bulletPosition = line;
	}

	public void undo() {
		(new UndoAction()).actionPerformed(null);
	}

	public void redo() {
		(new RedoAction()).actionPerformed(null);
	}

	@SuppressWarnings("serial")
	class UndoAction extends AbstractAction {
		//todo -> something wrong here.
		public UndoAction() {
			super("Undo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.undo();
			} catch (CannotUndoException ex) {
				System.out.println("Unable to undo.");
				ex.printStackTrace();
			}
			update();
			redoAction.update();
		}
		protected void update() {
			if(undo.canUndo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getUndoPresentationName());
			}
			else {
				setEnabled(false);
				putValue(Action.NAME, "Undo");
			}
		}
	} //UndoAction class


	@SuppressWarnings("serial")
	class RedoAction extends AbstractAction {
		public RedoAction() {
			super("Redo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.redo();
			}
			catch (CannotRedoException ex) {
				System.out.println("Unable to redo.");
				ex.printStackTrace();
			}
			update();
			undoAction.update();
		}

		protected void update() {
			if(undo.canRedo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getRedoPresentationName());
			}
			else {
				setEnabled(false);
				putValue(Action.NAME, "Redo");
			}
		}
	}// class RedoAction 


	/**
	 * Project.focusedEditor modifier.
	 *
	 */

	class EditorInternalFrameAdapter
	extends javax.swing.event.InternalFrameAdapter
	{
		protected Project prj;
		protected VeriSimDocumentFrame editor;

		public EditorInternalFrameAdapter(VeriSimDocumentFrame editor,
				Project prj) {
			this.prj = prj;
			this.editor = editor;
		}

		public void  internalFrameActivated(InternalFrameEvent ev ) {
			prj.focusedEditor = editor;
			prj.god.setTitle(prj.name + " - vide - [ " + editor.getTitle() + " ]");
			editor.focused = true;
			prj.enableEditActions(true);
		}

		public void internalFrameClosing(InternalFrameEvent ev) {
			if(getFileData().isModified) {
				Object[] options = { "Yes", "No"};
				if (JOptionPane.showOptionDialog(null, "File is modified. Save?\n" +
						editor.getFileName(),
						"File is modified. Save?",
						JOptionPane.DEFAULT_OPTION,
						JOptionPane.WARNING_MESSAGE,
						null,
						options, options[0]) == 0)
					try {
						editor.saveFile();
					} catch (IOException ioex) {
						prj.error("could not save \"" + getFileName() + "\": " +
								ioex);
					}
			}

			editor.focused = false;
			prj.removeVeriSimDocFrame(editor);
			prj.removeFromWindowsList(editor);
			editor.editor.getDocument().removeUndoableEditListener(editor); //vezi
			//comentariul de la declaratie

			prj.enableEditActions(false);
		}

		public void internalFrameDeactivated(InternalFrameEvent e) {
			prj.god.setTitle(prj.name + " - vide");
			prj.enableEditActions(false);
		}

	} //EditorInternalFrameAdapter class


	/**
	 * Displays line numbers on the left of editor
	 * todo -> must be a inner class of Project and alocated only once.
	 */

	class NumberedBorder extends javax.swing.border.EmptyBorder {

		int fontHeight;
		int startHeight = 0;
		int left;
		int endHeight = 0;
		JViewport vport;
		public int bulletPosition = 1;

		public NumberedBorder(int left, JViewport vport, int fontHeight) {
			super(0, left, 0, 0);
			this.left = left;
			this.vport = vport;
			this.fontHeight = fontHeight;
		}

		public void paintBorder(Component c, Graphics g, int x,
				int y, int width, int height)
		{
			//bug >>  if (vport.getViewPosition().x > left + 5) return; //the border is not shown.

			startHeight = vport.getViewPosition().y;
			endHeight = vport.getExtentSize().height + startHeight;
			g.setColor(Color.lightGray);
			g.fillRect(0, startHeight, left, endHeight);
			g.setColor(Color.black);
			int j = startHeight / fontHeight;
			for (int i = (j +1) * fontHeight - fontHeight/2; i < endHeight + 25; i += fontHeight, j++){ //j++ ?? whadda bad dream !
				g.drawString(j +1 + "", 0, i + 3);
			}

			g.setColor(Color.red);
			g.fillOval(left - 12, bulletPosition * fontHeight + 1, 10, 10);
		}

		void setFontHeight(int newH){
			fontHeight = newH;
		}
	}//NumberedBorder  class



	/**
	 * Displays caret position in the status bar.
	 * todo -> alocate only once. 
	 */

	class CaretPositionChangeListener implements ChangeListener {
		VeriSimFrame.StatusBar sb;
		DefaultCaret _caret;
		JTextArea _editor;
		int line;

		public CaretPositionChangeListener(VeriSimFrame.StatusBar sb,
				JTextArea _editor)
		{
			this._caret = (DefaultCaret) _editor.getCaret();
			this._editor = _editor;
			this.sb = sb;
		}

		public void stateChanged(ChangeEvent event) {
			int offset = _caret.getDot();
			try {
				line =_editor. getLineOfOffset(offset);
				sb.setLnCol(line, offset - _editor.getLineStartOffset(line));
				if(!_caret.isVisible())_caret.setVisible(true);
			} catch (Exception e) {
				System.out.println("Exception in caretListener.stateChanged:" + e);
			}
		}
	}//CaretPositionChangeListener class
}//VeriSimDocumentFrame class
//sa le ia dracu' de acolade.


